package org.javanum.number;

import static org.javanum.number.TestUtils.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class ComplexTest {
    private static final double DELTA = Math.pow(1.0,-10);
    private final Complex firstNum;
    private final Complex secondNum;
    private final Real real;

    public ComplexTest(Complex firstNum, Complex secondNum, Real real){
        this.firstNum=firstNum;
        this.secondNum=secondNum;
        this.real = real;
    }

    @Parameters
    public static Collection<Object[]> getParameters(){

        Random rand = new Random();
        int randomDataValue = 10;
        List<Object[]> data = new ArrayList<Object[]>(randomDataValue);
        for(int i=1;i<=randomDataValue;i++){
            int r1 = Math.abs(rand.nextInt(i));
            double rand1 = rand.nextDouble()*r1;
            int r2 = Math.abs(rand.nextInt(i));
            double rand2 = rand.nextDouble()*r2;
            int r3 = Math.abs(rand.nextInt(i));
            double rand3 = rand.nextDouble()*r3;
            int r4 = Math.abs(rand.nextInt(i));
            double rand4 = rand.nextDouble()*r4;
            data.add(new Object[]
                    {Complex.valueOf(rand1,rand2),
                            Complex.valueOf(rand3,rand4),
                            Real.valueOf(rand3)});
        }
        return data;
    }

/*-------------------------------------------------------------*/
    /*Equals tests*/

    @Test
    public void testEqualsElementsEqual(){
        Complex c = getOneNumber();
        assumeNotNull(c);
        Complex d = Complex.valueOf(c.real,c.imaginary);

        assertTrue(c+"!="+d,c.equals(d));
    }

    @Test
    public void testNonEqualsElementsNonEquals(){
        assumeNotNull(firstNum,secondNum);

        Complex c = firstNum;
        Complex d = secondNum;

        assumeTrue(!c.realPart().equals(d.realPart()));
        assumeTrue(!c.imaginaryPart().equals(d.imaginaryPart()));

        assertFalse(
                String.format(".equals() indicates that %1$s==%2$s",c,d),
                c.equals(d));
    }

    @Test
    public void testEqualsReflexivityContract(){
        Complex c = getOneNumber();
        assumeNotNull(c);

        assertTrue(
                String.format(
                        "Reflexivity fails: " +
                                "%1$s does not equal itself " +
                                "according to .equals()",c),c.equals(c));
    }

    @Test
    public void testEqualsSymmetryContract(){
        Complex r = getOneNumber();
        assumeNotNull(r);
        Complex c = Complex.valueOf(r.real,r.imaginary);

        assumeTrue(r.equals(c));

        assertTrue(
                String.format(
                        "Symmetry fails: %1$s.equals(%2$s)==true, " +
                                "but %2$s.equals(%1$s)==false",r,c),c.equals(r));
    }

    @Test
    public void testEqualsTransitivityContract(){
        Complex x = getOneNumber();
        Complex y = Complex.valueOf(x.real,x.imaginary);
        assumeTrue(x.equals(y));
        Complex z = Complex.valueOf(y.real,y.imaginary);
        assumeTrue(y.equals(z));

        assertTrue(
                String.format("Transitivity fails: " +
                        "%1$s.equals(%2$s), and %2$s.equals(%3$s), " +
                        "but %1$s.equals(%3$s)==false",x,y,z),x.equals(z));
    }

    @SuppressWarnings({"ObjectEqualsNull"})
    @Test
    public void testEqualsNullityContract(){
        Complex c = getOneNumber();

        assertFalse(
                String.format(
                        "%1$s is non-null, " +
                                "but .equals(null) returns true",c),
                c.equals(null));
    }

/*-------------------------------------------------------------*/
    /*Hashcode tests*/

    @Test
    public void testHashCodeSameForEqualElements(){
        Complex c = getOneNumber();
        assumeNotNull(c);

        Complex d = Complex.valueOf(c.real,c.imaginary);
        assumeTrue(c.equals(d));

        assertEquals(
                String.format(
                        "%1$s.hashCode()!=%2$s.hashCode(), " +
                                "yet %1$s.equals(%2$s)",c,d),
                c.hashCode(),d.hashCode());
    }

    @Test
    public void testHashCodeNotSameForUnequalElements(){
        assumeNotNull(firstNum,secondNum);

        assumeTrue(!firstNum.equals(secondNum));

        //don't try to hash infinities
        assumeTrue(!firstNum.equals(Complex.COMPLEX_INFINITY));
        assumeTrue(!secondNum.equals(Complex.COMPLEX_INFINITY));

        assertFalse(firstNum+".hashCode()=="+secondNum+
                ".hashCode(), but " +
                firstNum+".equals("+secondNum+
                ") returns false",
                firstNum.hashCode()==secondNum.hashCode());
    }

/*-------------------------------------------------------------*/
    /*Addition tests*/

    @Test
    public void testComplexAdd(){
        assumeNotNull(firstNum,secondNum);
        assumeNotNaN(firstNum);
        assumeTrue(!Complex.isNaN(secondNum));
        assumeNotComplexInfinity(firstNum,secondNum);
        assumeTrue(!Complex.isInfinite(secondNum));

        Complex f = firstNum.add(secondNum);

        double real = firstNum.realPart().value()+
                secondNum.realPart().value();
        double imag = firstNum.imaginaryPart().value()+
                secondNum.imaginaryPart().value();

        assertEquals("Real parts do not match: "+
                f.realPart().value()+"!="+real,real,
                f.realPart().value(),DELTA);
        assertEquals("Imaginary parts do not match: "+
                f.imaginaryPart().value()+"!="+imag,imag,
                f.imaginaryPart().value(),DELTA);
    }

    @Test
    public void testRealAdd() throws Exception {
        assumeNotNull(firstNum,real);
        assumeNotNaN(firstNum);
        assumeNotNaN(real);
        assumeNotComplexInfinity(firstNum);
        assumeNotRealInfinity(real);

        Complex add = firstNum.add(real);
        double re = firstNum.real+ real.value;
        double imag = firstNum.imaginary;

        assertEquals(TestUtils.format("a+ib+r!=a+r+ib" +
                "--real parts do not match","z",firstNum,"real",real),
                re,add.real,DELTA);
        assertEquals(TestUtils.format("a+ib+r!=a+r+ib" +
                "--imaginary parts do not match","z",firstNum,"real",real),
                imag,add.imaginary,DELTA);
    }

/*-------------------------------------------------------------*/
    /*Subtraction tests*/

    @Test
    public void testComplexSubtract(){
        assumeNotNull(firstNum,secondNum);
        assumeNotNaN(firstNum,secondNum);
        assumeNotComplexInfinity(firstNum,secondNum);

        Complex result = firstNum.subtract(secondNum);
        double real = firstNum.realPart().value()-secondNum.
                realPart().value();
        double imag = firstNum.imaginaryPart().value()-secondNum.
                imaginaryPart().value();


        assertEquals("Real parts do not match: "+
                result.realPart().value()+"!="+
                real,real,result.realPart().value(),DELTA);
        assertEquals("Imaginary parts do not match: "+
                result.imaginaryPart().value()+"!="+
                imag,imag,result.imaginaryPart().value(),DELTA);
    }

    @Test
    public void testRealSubtract() throws Exception {
        assumeNotNull(firstNum);
        assumeNotNaN(firstNum);
        assumeNotComplexInfinity(firstNum);
        assumeNotRealInfinity(real);

        Complex sub = firstNum.subtract(real);

        double re = firstNum.real-real.value;
        double imag = firstNum.imaginary;

        Complex correctValue = Complex.valueOf(re,imag);

        assertComplexEquality("a+ib-r!=a-r+ib",correctValue,sub,DELTA);
    }

    /*-------------------------------------------------------------*/
    /*Multiplication tests*/

    @Test
    public void testComplexMultiply(){
        assumeNotNull(firstNum,secondNum);
        assumeTrue(!Complex.ZERO.equals(secondNum));

        Complex result = firstNum.multiply(secondNum);

        double realPart = firstNum.realPart().value()*
                secondNum.realPart().value()-
                firstNum.imaginaryPart().value()*
                        secondNum.imaginaryPart().value();
        double imagPart = firstNum.realPart().value()*
                secondNum.imaginaryPart().value()+
                firstNum.imaginaryPart().value()*
                        secondNum.realPart().value();

        assertEquals("Real parts do not match: "+
                result.realPart().value()+"!="+realPart,
                result.realPart().value(),
                realPart,
                DELTA);
        assertEquals("Imaginary parts do not match: "+
                result.imaginaryPart().value()+"!="+imagPart,
                result.imaginaryPart().value(),
                imagPart,
                DELTA);
    }
    @Test
    public void testRealMultiply() {
        assumeNotNull(firstNum,real);
        assumeNotComplexInfinity(firstNum);
        assumeNotNaN(firstNum);
        assumeNotNaN(real);
        assumeNotRealInfinity(real);

        Complex result = firstNum.multiply(real);

        double re = firstNum.real*real.value;
        double imag = firstNum.imaginary*real.value;

        Complex correct = Complex.valueOf(re,imag);

        assertComplexEquality("(a+ib)*r!=ar+ibr",correct,result,DELTA);
    }


/*-------------------------------------------------------------*/
    /*Division tests*/
    @Test
    public void testComplexDivide(){
        assumeNotNull(firstNum,secondNum);
        assumeNotZero(secondNum);
        assumeNotNaN(firstNum,secondNum);
        assumeNotComplexInfinity(firstNum,secondNum);

        Complex result = firstNum.divide(secondNum);

        Complex mult = result.multiply(secondNum);

        assertEquals(String.format("Real parts do not match: " +
                "firstNum=%1$s, result=%2$s",firstNum,result)
                ,firstNum.real,mult.real,DELTA);
        assertEquals(String.format("Imaginary parts do not match: " +
                "firstNum=%1$s, result=%2$s",firstNum,result)
                ,firstNum.imaginary,mult.imaginary,DELTA);

    }

    @Test
    public void testRealDivide() throws Exception {
        assumeNotNull(firstNum,real);
        assumeNotComplexInfinity(firstNum);
        assumeNotRealInfinity(real);
        assumeNotNaN(firstNum);
        assumeNotNaN(real);
        assumeNotZero(real);
        
        Complex result = firstNum.divide(real);

        double re = firstNum.real/real.value;
        double imag = firstNum.imaginary/real.value;

        assertComplexEquality("(a+ib)/r!=a/r+ib/r",
                    Complex.valueOf(re,imag),result,DELTA);
    }

    /*-------------------------------------------------------------*/
    /*Conjugate, Modulus, Negation, and Conversion tests*/

    @Test
    public void testConjugate(){
        Complex c = getOneNumber();
        assumeNotNull(c);
        assumeTrue(!Complex.isInfinite(c));
        assumeTrue(!Complex.isNaN(c));

        Complex conjugate = c.conjugate();

        assertEquals("Conjugate real part does not match: "+
                conjugate.realPart().value()+"!="+
                c.realPart().value(),c.realPart().value(),
                conjugate.realPart().value(),DELTA);
        assertEquals("Conjugate imaginary part does not " +
                "match its negative: "+
                conjugate.imaginaryPart().value()+"!=-("+
                c.imaginaryPart().value()+")",c.imaginaryPart().value(),
                -conjugate.imaginaryPart().value(),DELTA);
    }

    @Test
    public void testModulus(){
        /*
        This tests the fact that scaling a Complex number c down by
        its own modulus should result in a complex number d whose
        modulus is 1
         */
        Complex c = getOneNumber();
        assumeNotNull(c);
        assumeNotNaN(c);
        assumeNotComplexInfinity(c);
        assumeNotZero(c);

        double modulus = c.modulus();
        Complex d =
                Complex.valueOf(c.real/modulus,c.imaginary/modulus);

        double shouldBeOneModulus = d.modulus();

        assertEquals("Scaling by modulus() does not " +
                "result in a number with modulus 1",
                1.0,shouldBeOneModulus,DELTA);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    @Test
    public void testConvertToPolarForm(){
        Complex c = getOneNumber();
        assumeNotNull(c);
        assumeNotComplexInfinity(c);
        assumeNotNaN(c);
        assumeNotZero(c);
        assumeTrue(Double.compare(c.real,0d)!=0);

        PolarComplex result = c.polarForm();

        double correctMagnitude = c.modulus();
        double correctAngle = Math.atan(c.imaginary/c.real);

        assertComplexEquality("Conversion to Polar Form incorrect.",
                PolarComplex.valueOf(correctMagnitude,correctAngle),result,DELTA);
    }

    @Test
    public void testPolarFormCartesianForm() throws Exception {
        /*
        Tests that z.polarForm().cartesianForm()=z for and
        Complex z
         */
        Complex z = getOneNumber();
        assumeNotNull(z);
        assumeNotComplexInfinity(z);
        assumeNotNaN(z);

        PolarComplex inter = z.polarForm();
        Complex polarAndBack = inter.cartesianForm();

        assertComplexEquality("z.polarForm().cartesianForm()!=z",
                                z,polarAndBack,DELTA);
    }

    

/*-------------------------------------------------------------*/
    /*Helper methods*/

    private Complex getOneNumber(){
        if(firstNum==null){
            return secondNum;
        }else
            return firstNum;
    }



}
